package com.wpam.scrabble.mobile.communication.bt;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Parcelable;
import android.widget.TextView;
import android.widget.Toast;

import com.wpam.scrabble.mobile.GameLogic;
import com.wpam.scrabble.mobile.R;
import com.wpam.scrabble.mobile.activities.ServerInfo;
import com.wpam.scrabble.mobile.activities.ServerSearchActivity;
import com.wpam.scrabble.mobile.activities.ServerSearchActivity.ServerAdapter;
import com.wpam.scrabble.mobile.communication.InterfaceManager;
import com.wpam.scrabble.mobile.messages.Message;
import com.wpam.scrabble.mobile.messages.MsgSerializer;

public class BluetoothManager implements InterfaceManager {
	public final static UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	
	public final static int BUFFER_SIZE = 1024;
	
	public final static int REQUEST_ENABLE_BT = 1;
	public final static int REQUEST_ENABLE_DISCOVERABILITY = 2;

	public static final long TIMEOUT = 5000;
	public static final long LONG_TIMEOUT = 20000;
	
	private static BluetoothManager instance = null;
	private Activity parentActivity_;
	private BluetoothAdapter btAdapter_;
	
	private BtClientManager clientManager_;
	private BtServerManager serverManager_;

	private DisconnectListener disconnectReceiver_;
	private DiscoveryListener discoveryReceiver_;
	
	public static BluetoothManager getInstance(Activity parent) {
		
		if (instance == null)
			instance = new BluetoothManager(parent);
		else
			instance.parentActivity_ = parent;
		return instance;
	}

	public static BluetoothManager getInstance() {
		return instance;
	}
	
	private BluetoothManager(Activity parent) {
		
		this.parentActivity_ = parent;
		this.btAdapter_ = BluetoothAdapter.getDefaultAdapter();
		this.clientManager_ = null;
		this.serverManager_ = null;
		this.disconnectReceiver_ = null;
		this.discoveryReceiver_ = null;
	}

	void initializeClient() {
		clientManager_ = new BtClientManager(parentActivity_);
	}
	
	void initializeServer() {
		serverManager_ = new BtServerManager(parentActivity_);
	}
	
	public BtClientManager getBtClientManager() {
		return clientManager_;
	}
	
	public BtServerManager getBtServerManager() {
		return serverManager_;
	}
	
	public boolean removeFromConnectedClients(BluetoothDevice o) {
		
		if (serverManager_ != null)
			return serverManager_.removeFromConnectedClients(o);
		else 
			assert false : "Should never happen.";
		return false;
	}
	
	public Activity getParentActivity() {
		return parentActivity_;
	}
	
	BluetoothAdapter getAdapter() {
		return btAdapter_;
	}
	
	public void startReconnectTask() {
		clientManager_.startReconnectTask();
	}
	
	public void stopReconnectTask() {
		clientManager_.stopReconnectTask();
	}
	
	public void stopAcceptorTask() {
		serverManager_.stopAcceptorTask();
	}

	public void stopCurrentActions() {
		
		cancelDiscovery();
		stopDisconnectListener();
		stopDiscoveryListener();
		if (clientManager_ != null) {
			clientManager_.stopRunningTasks();
			clientManager_.disconnect();
			clientManager_ = null;
		}
		if (serverManager_ != null) {
			serverManager_.stopRunningTasks();
			serverManager_.disconnectAllClients();
			serverManager_.stopAcceptorTask();
			serverManager_ = null;
		}
	}
	
	public boolean isBtEnabled() {
		return btAdapter_.isEnabled();
	}
	
	void startDiscovery() {
		btAdapter_.startDiscovery();
	}
	
	Set<BluetoothDevice> getBondedDevices() {
		return btAdapter_.getBondedDevices();
	}
	
	public boolean isBtAvailable() {
		return (null != btAdapter_);
	}
	
	public void turnRadioOn() {
		
    	Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
		parentActivity_.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
	}
	
	public void setDiscoverable(int seconds) {
		
		if (serverManager_ == null)
			initializeServer();
		Intent enableDiscoverabilityIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		enableDiscoverabilityIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, seconds);
		parentActivity_.startActivityForResult(enableDiscoverabilityIntent, REQUEST_ENABLE_DISCOVERABILITY);  
	}
	
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		
		if (requestCode == REQUEST_ENABLE_DISCOVERABILITY) {
			if (resultCode != Activity.RESULT_CANCELED) {
				BtServerManager.AcceptorTask task = serverManager_.new AcceptorTask();
				task.execute();
				Toast.makeText(parentActivity_.getApplicationContext(), R.string.waiting_for_incoming_connections_toast, Toast.LENGTH_SHORT).show();
				return;
			}
		}
		else if (requestCode == REQUEST_ENABLE_BT) {
			if (resultCode != Activity.RESULT_CANCELED) {
				runServerSearch();
				return;
			}
		}
		if (resultCode == Activity.RESULT_CANCELED)
			parentActivity_.finish();
	}

	public void closeConnection(DeviceNetworkInfo info) {
		
		if (serverManager_ != null)
			serverManager_.removeFromConnectedClients(info);
		
		closeDevice(info.getSock(), info.getInputStream(), info.getOutputStream());
	}
	
	public void closeConnection(BluetoothSocket sock, InputStream inputStream, OutputStream outputStream) {
		
		if (serverManager_ != null) {
			for (DeviceNetworkInfo device : serverManager_.getConnectedPlayers())
				if (device.getSock().equals(sock))
					serverManager_.removeFromConnectedClients(device);
		}
		
		closeDevice(sock, inputStream, outputStream);
	}
	
	private void closeDevice(BluetoothSocket sock, InputStream inputStream, OutputStream outputStream) {
		
		try {
			if (inputStream != null) {
				synchronized(inputStream) {
					inputStream.close();
					inputStream = null;
				}
			}
			if (outputStream != null) {
				synchronized (outputStream) {
					outputStream.close();
					outputStream = null;
				}	
			}
			if (sock != null) {
				synchronized (sock) {
					sock.close();
					sock = null;
				}
			}
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void runServerSearch() {
		
		if (clientManager_ == null) 
			initializeClient();
		BtClientManager.FindServersTask task = clientManager_.new FindServersTask();
		task.execute();
	}
	
	public void runConnect(BluetoothDevice device) {
		
		if (clientManager_ == null)
			initializeClient();
		BtClientManager.ConnectTask task = clientManager_.new ConnectTask();
		task.execute(device);
	}

	private void startInGameAcceptor() {
		
		if (serverManager_ == null)
			initializeServer();
		serverManager_.startInGameAcceptor();
	}
	
	void cancelDiscovery() {
		
		if (btAdapter_.isDiscovering())
			btAdapter_.cancelDiscovery();
	}

	public void disconnectClient() {
		
		if (clientManager_ != null)
			clientManager_.disconnect();
	}

	public void disconnectAllClients() {
		
		if (serverManager_ != null)
			serverManager_.disconnectAllClients();
	}

	public String getMyName() {
		return btAdapter_.getName();
	}

	public void startDisconnectListener() {
		
		if (disconnectReceiver_ == null)
			disconnectReceiver_ = new DisconnectListener();
		
		parentActivity_.getApplicationContext().registerReceiver(disconnectReceiver_, disconnectReceiver_.getFilter());
	}

	public void resumeDisconnectListener() {
		parentActivity_.getApplicationContext().registerReceiver(disconnectReceiver_, disconnectReceiver_.getFilter());
	}

	public void stopDisconnectListener() {
		
		if (disconnectReceiver_ != null) {
			try {
				parentActivity_.getApplicationContext().unregisterReceiver(disconnectReceiver_);
			} catch (IllegalArgumentException ex) {
				ex.printStackTrace();
			}
		}
	}
	
	public void startDiscoveryListener(ServerSearchActivity parent, ServerAdapter adapter, TextView status) {
		
		if (discoveryReceiver_ == null)
			discoveryReceiver_ = new DiscoveryListener(parent, adapter, status);

		parent.getApplicationContext().registerReceiver(discoveryReceiver_, discoveryReceiver_.getFilter());
	}
	
	public void resumeDiscoveryListener() {
		parentActivity_.getApplicationContext().registerReceiver(discoveryReceiver_, discoveryReceiver_.getFilter());
	}

	public void stopDiscoveryListener() {
		
		if (discoveryReceiver_ != null) {
			try {
				parentActivity_.getApplicationContext().unregisterReceiver(discoveryReceiver_);
			} catch (IllegalArgumentException ex) {
				ex.printStackTrace();
			}
		}
	}

	public void disconnectSelfGently() throws IOException {
		
		if (serverManager_ != null)
			serverManager_.disconnectGently();
		else if (clientManager_ != null)
			clientManager_.disconnectGently();
	}

	public boolean isPlayerConnected(String name) {
		
		if (serverManager_ != null)
			return serverManager_.isPlayerConnected(name);
		return false;
	}

	public void disconnectSingleClient(BluetoothDevice senderDevice) {

		for (DeviceNetworkInfo device : serverManager_.getConnectedPlayers()) {
			if (device.getSock().getRemoteDevice().equals(senderDevice))
				BluetoothManager.getInstance().closeConnection(device);
		}
		serverManager_.removeFromConnectedClients(senderDevice);
	}

	public void writeAsServer(DeviceNetworkInfo receiver, byte[] buffer) {
		serverManager_.write(receiver, buffer);
	}

	public void writeAsServer(BluetoothDevice receiver, byte[] buffer) throws Exception {
		serverManager_.write(receiver, buffer);
	}

	public void writeAsClient(byte[] buffer) throws IOException {
		clientManager_.write(buffer);
	}

	public void forwardMessage(Message message) {

		for (DeviceNetworkInfo receiver : getConnectedPlayers()) {
			if (message.getSenderDevice() == null || !receiver.getSock().getRemoteDevice().equals(message.getSenderDevice())) 
				writeAsServer(receiver, MsgSerializer.serialize(message));
		}
	}

	public ArrayList<DeviceNetworkInfo> getConnectedPlayers() {
		return serverManager_.getConnectedPlayers();
	}
	
	private class DisconnectListener extends BroadcastReceiver {

		private IntentFilter filter_;
		
		public DisconnectListener() {
			this.filter_ = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
		}
		
		public IntentFilter getFilter() {
			return filter_;
		}
		
		@Override
		public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();
            if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
				if (serverManager_ != null) {
            		startInGameAcceptor();
					BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
					if (BluetoothManager.getInstance().removeFromConnectedClients(device))
						GameLogic.getInstance().playerDisconnected(GameLogic.getInstance().getPlayerManager().getNameOfSender(device));
				} else
					GameLogic.getInstance().serverDisconnected();
            }
		}
	}
	
	private class DiscoveryListener extends BroadcastReceiver {
		
		private ServerSearchActivity discoveryParentActivity_;
		private ServerAdapter adapter_;
		private TextView statusText_;
		private IntentFilter filter_;
		
		public DiscoveryListener(ServerSearchActivity parentActivity, ServerAdapter adapter, TextView statusText) {
			
			this.discoveryParentActivity_ = parentActivity;
			this.adapter_ = adapter;
			this.statusText_ = statusText;
			this.filter_ = new IntentFilter(BluetoothDevice.ACTION_FOUND);
			filter_.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
			filter_.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
				filter_.addAction(BluetoothDevice.ACTION_UUID);
		}
		
		public IntentFilter getFilter() {
			return filter_;
		}
		
		@Override
		public void onReceive(Context context, Intent intent) {
			
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                adapter_.add(new ServerInfo(device, false));
			}
            else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
            	discoveryParentActivity_.uiSetStatus(discoveryParentActivity_.getResources().getString(R.string.search_amongst_all), true, false);
            }
            else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            	if (statusText_.getText().equals(discoveryParentActivity_.getResources().getString(R.string.search_amongst_all)) 
            			|| statusText_.getText().equals(discoveryParentActivity_.getResources().getString(R.string.search_amongst_paired)))
            		discoveryParentActivity_.uiSetStatus(discoveryParentActivity_.getResources().getString(R.string.search_completed), false, true);
            }
            else if (BluetoothDevice.ACTION_UUID.equals(action)) {
            	BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            	Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
            	for (Parcelable uuid : uuids) {
					if (uuid.equals(BluetoothManager.MY_UUID)) {
						adapter_.add(new ServerInfo(device, true));
						adapter_.notifyDataSetChanged();
						break;
					}
				}
            }
	    }
	}
	
}
